package pl.edu.pb.wi.cookbook.controller;

import java.util.Arrays;
import java.util.Date;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import net.tanesha.recaptcha.ReCaptchaImpl;
import net.tanesha.recaptcha.ReCaptchaResponse;

import org.apache.commons.lang3.SerializationUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import pl.edu.pb.wi.cookbook.entity.User;
import pl.edu.pb.wi.cookbook.form.EditUserForm;
import pl.edu.pb.wi.cookbook.form.LoginForm;
import pl.edu.pb.wi.cookbook.form.MailForm;
import pl.edu.pb.wi.cookbook.form.RegisterForm;
import pl.edu.pb.wi.cookbook.service.UserService;
import pl.edu.pb.wi.cookbook.session.UserSession;
import pl.edu.pb.wi.cookbook.util.HashUtil;

import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskOptions;

@Controller
public class UserController {
    private static final Logger log = Logger.getLogger(UserController.class
            .getName());
    private Queue emailQueue;
    private UserSession userSession;
    private UserService userService;
    private String privateRecaptchaKey;

    @Autowired
    public UserController(UserSession userSession, UserService userService) {
        this.userSession = userSession;
        this.userService = userService;
        this.emailQueue = QueueFactory
                .getQueue(SendEmailsController.EMAIL_QUEUE_NAME);
    }

    @Value("#{captchaProperties.recaptcha_private_key}")
    public void setPrivateRecaptchaKey(String privateRecaptchaKey) {
        this.privateRecaptchaKey = privateRecaptchaKey;
    }

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login(Model model) {
        model.addAttribute("form", new LoginForm());

        return "login";
    }

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String login(@ModelAttribute("form")
    @Valid
    LoginForm form, BindingResult results, Model model) {
        String returnPath = "login";

        if (results.hasErrors()) {
            model.addAttribute("errorMsgs",
                    Arrays.asList(new String[] { "Uzupełnij wymagane pola." }));

            return returnPath;
        }

        try {
            User userByLogin = userService.getUserByLogin(form.getLogin());

            if (userByLogin != null) {
                String hashedPassword = HashUtil.hashInput(form.getPassword());
                if (userByLogin.getPassword().equals(hashedPassword)) {
                    // Poprawny login i hasło.

                    userByLogin.setLastLoginAt(new Date());
                    userService.updateUser(userByLogin);

                    userSession.setUser(userByLogin);

                    returnPath = "main";
                } else {
                    // Złe hasło.
                    model.addAttribute(
                            "errorMsgs",
                            Arrays.asList(new String[] { "Podano niepoprawne dane." }));
                }
            } else {
                // Zły login.
                model.addAttribute("errorMsgs", Arrays
                        .asList(new String[] { "Podano niepoprawne dane." }));
            }
        } catch (Exception e) {
            log.info(String.format("Problem with login form. Exception: %s",
                    e.getMessage()));

            model.addAttribute(
                    "errorMsgs",
                    Arrays.asList(new String[] { "Wystąpił błąd. Spróbuj ponownie." }));

            returnPath = "message";
        }

        return returnPath;
    }

    @RequestMapping(value = "/register", method = RequestMethod.GET)
    public String register(Model model) {
        model.addAttribute("form", new RegisterForm());

        return "register";
    }

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String register(@ModelAttribute("form")
    @Valid
    RegisterForm form, BindingResult results, Model model,
            @RequestParam("recaptcha_challenge_field")
            String challenge, @RequestParam("recaptcha_response_field")
            String response, HttpServletRequest req) {
        String returnPath = "register";

        if (results.hasErrors()) {
            model.addAttribute(
                    "errorMsgs",
                    Arrays.asList(new String[] { "Prawidłowo uzupełnij wszystkie pola." }));

            return returnPath;
        }

        if ((challenge == null) || (response == null)) {
            model.addAttribute(
                    "errorMsgs",
                    Arrays.asList(new String[] { "reCatpcha: Słowa, które zostały wpisane nie pasują do tych z obrazka." }));

            return returnPath;
        }

        String remoteAddr = req.getRemoteAddr();
        ReCaptchaImpl reCaptcha = new ReCaptchaImpl();

        reCaptcha.setPrivateKey(privateRecaptchaKey);

        ReCaptchaResponse reCaptchaResponse = reCaptcha.checkAnswer(remoteAddr,
                challenge, response);

        if (!reCaptchaResponse.isValid()) {
            model.addAttribute(
                    "errorMsgs",
                    Arrays.asList(new String[] { "reCatpcha: Słowa, które zostały wpisane nie pasują do tych z obrazka." }));

            return returnPath;
        }

        try {
            if (form.getPassword().equals(form.getPassword2())) {
                User userCheck = userService.getUserByLogin(form.getLogin());

                if (userCheck == null) {
                    userCheck = userService.getUserByEmail(form.getEmail());

                    if (userCheck == null) {
                        User user = userService.createUser(form.getLogin(),
                                form.getPassword(), form.getEmail());

                        addMailVerificationToQueue(user);

                        model.addAttribute(
                                "successMsgs",
                                Arrays.asList(new String[] { "Konto zostało pomyślnie założone. Aby w pełni odblokować możliwości profilu musisz zweryfikować adres e-mail. Aby to zrobić kliknij link zwarty w mailu, który został do ciebie wysłany. Możesz poprosić o ponowne wysłanie wiadomości w każdym momencie, korzystając w górnego menu." }));

                        returnPath = "message";
                    } else {
                        results.addError(new FieldError("form", "email",
                                "Podany e-mail jest już związany z innym kontem."));
                    }
                } else {
                    results.addError(new FieldError("form", "login",
                            "Podany login jest zajęty."));
                }
            } else {
                results.addError(new FieldError("form", "password",
                        "Hasła nie są takie same."));
                results.addError(new FieldError("form", "password2",
                        "Hasła nie są takie same."));
            }
        } catch (Exception e) {
            log.info(String.format("Problem with register form. Exception: %s",
                    e.getMessage()));

            model.addAttribute(
                    "errorMsgs",
                    Arrays.asList(new String[] { "Wystąpił błąd. Spróbuj ponownie." }));

            returnPath = "message";
        }

        return returnPath;
    }

    @RequestMapping(value = "/user/edit", method = RequestMethod.GET)
    public String edit(Model model) {
        model.addAttribute("form", new EditUserForm());

        return "user/edit";
    }

    @RequestMapping(value = "/user/edit", method = RequestMethod.POST)
    public String edit(@ModelAttribute("form")
    @Valid
    EditUserForm form, BindingResult results, Model model) {
        String returnPath = "user/edit";

        if ((form.getEmail() == null || form.getEmail().isEmpty())
                && (form.getNewPassword() == null || form.getNewPassword()
                        .isEmpty())
                && (form.getNewPassword2() == null || form.getNewPassword2()
                        .isEmpty())) {
            model.addAttribute(
                    "errorMsgs",
                    Arrays.asList(new String[] { "Jeżeli chcesz zmienić e-mail lub hasło musisz uzupełnić odpowiednie pola." }));

            return returnPath;
        }

        if ((form.getNewPassword() != null || !form.getNewPassword().isEmpty())
                && (form.getNewPassword2() != null || !form.getNewPassword2()
                        .isEmpty())
                && !form.getNewPassword().equals(form.getNewPassword2())) {
            results.addError(new FieldError("form", "newPassword",
                    "Hasła nie są takie same."));
            results.addError(new FieldError("form", "newPassword2",
                    "Hasła nie są takie same."));
        }

        if (results.hasErrors()) {
            return returnPath;
        }

        User user = userSession.getUser();
        String oldPass = HashUtil.hashInput(form.getOldPassword());
        if (!user.getPassword().equals(oldPass)) {
            results.addError(new FieldError("form", "oldPassword",
                    "Hasło nie jest poprawne."));

            return returnPath;
        }

        try {
            if (form.getNewPassword() != null
                    && !form.getNewPassword().isEmpty()
                    && form.getNewPassword2() != null
                    && !form.getNewPassword2().isEmpty()
                    && form.getNewPassword().equals(form.getNewPassword2())) {
                if (form.getNewPassword().length() < 5) {
                    results.addError(new FieldError("form", "newPassword",
                            "Wpisz minimum 5 znaków."));
                    results.addError(new FieldError("form", "newPassword2",
                            "Wpisz minimum 5 znaków."));

                    return returnPath;
                }

                user.setPassword(HashUtil.hashInput(form.getNewPassword()));
            }

            if (form.getEmail() != null && !form.getEmail().isEmpty()
                    && !user.getEmail().equals(form.getEmail())) {
                user.setEmail(form.getEmail());
                user.setValidationHash(String.format("%s%s",
                        HashUtil.hashInput(user.getLogin()),
                        HashUtil.hashInput(form.getEmail())));
                user.setValidated(false);

                addMailVerificationToQueue(user);
            }

            userService.updateUser(user);

            model.addAttribute(
                    "successMsgs",
                    Arrays.asList(new String[] { "Zmiany zostały poprawnie zapisane." }));
        } catch (Exception e) {
            log.info(String.format(
                    "Problem with data change form. Exception: %s",
                    e.getMessage()));

            model.addAttribute(
                    "errorMsgs",
                    Arrays.asList(new String[] { "Wystąpił błąd. Spróbuj ponownie." }));

            returnPath = "message";
        }

        return returnPath;
    }

    @RequestMapping("/user/verify")
    public String verify(Model model) {
        User user = userSession.getUser();
        if (user == null || user.isValidated()) {
            return "main";
        } else {
            try {
                addMailVerificationToQueue(user);

                model.addAttribute(
                        "infoMsgs",
                        Arrays.asList(new String[] { "Wiadomość została wysłana na podany w profilu adres." }));
            } catch (Exception ex) {
                log.info(String.format(
                        "Problem with verification resend. Exception: %s",
                        ex.getMessage()));

                model.addAttribute(
                        "errorMsgs",
                        Arrays.asList(new String[] { "Wystąpił błąd. Spróbuj ponownie. Jeżeli błąd będzie się powtarzał skontaktuj się z administratorem." }));
            }

            return "message";
        }
    }

    @RequestMapping("/user/logout")
    public String logout() {
        userSession.setUser(null);

        return "main";
    }

    @RequestMapping("/verify/{hash}")
    public String verify(@PathVariable("hash")
    String hash, Model model) {
        String returnPath = "message";

        if (hash == null || hash.isEmpty()) {
            model.addAttribute(
                    "errorMsgs",
                    Arrays.asList(new String[] { "Podany adres jest nieprawidłowy." }));

            return returnPath;
        }

        try {
            User user = userService.getUserByValidationHash(hash);
            if (user != null) {
                if (!user.isValidated()) {
                    user.setValidated(true);

                    userService.updateUser(user);

                    model.addAttribute(
                            "successMsgs",
                            Arrays.asList(new String[] { "Gratulacje. Twoje konto zostało poprawnie aktywowane." }));
                } else {
                    model.addAttribute(
                            "infoMsgs",
                            Arrays.asList(new String[] { "Twoje konto jest już aktywne." }));
                }
            } else {
                model.addAttribute(
                        "errorMsgs",
                        Arrays.asList(new String[] { "Podany adres jest nieprawidłowy." }));
            }
        } catch (Exception ex) {
            log.info(String.format("Problem with verification. Exception: %s",
                    ex.getMessage()));
        }

        return returnPath;
    }

    private void addMailVerificationToQueue(User user) {
        MailForm mailForm = new MailForm(user.getEmail(),
                user.getValidationHash());
        emailQueue.add(TaskOptions.Builder.withMethod(TaskOptions.Method.PULL)
                .payload(SerializationUtils.serialize(mailForm)));
    }
}
